
package estadistec.logica.dependencias;

import estadistec.logica.parser.IEvaluable;
import java.util.Hashtable;

import estadistec.logica.tipos.TipoValor;

import estadistec.excepciones.ExcepcionLlaveInexistente;
import estadistec.excepciones.ExcepcionLlaveExistente;
import estadistec.logica.gestores.IManejable;
import estadistec.logica.parser.EstructuraEvaluable;

/**
 * Representa un elemento base. Un elemento base es un elemento que es parte
 * del grafo de dependencias. Por ejemplo un atributo.
 *
 * Para el grafo de dependencias se manejan dos árboles: el de la relación de 
 * superiores (ISuperior), que corresponden a aquellos elementos que 
 * afectan al nodo actual, y el de relación de dependientes (IDependiente)
 * que son aquellos nodos afectados cuando se modifica el elemento actual. 
 */

public abstract class Nodo
	implements INodo, ISuperior<Nodo>, IDependiente<Nodo>, IManejable
{
	/**
	 * Almacena los dependientes del gestionable
	 */
	protected Hashtable<Long, Nodo> _dependientes;

	/**
	 * Almacena los superiores del gestionable
	 */
	protected Hashtable<Long, Nodo> _superiores;

	/**
	 * Esta es la llave única que se le va a asignar para que pueda ser
	 * gestionado
	 */
	protected Long _llave;

	/**
	 * Devuelve la llave del objeto.
	 */
	public Long getLlave()
	{
		return this._llave;
	}

	/**
	 * Estructura que corresponde a la fórmula.
	 */
	protected EstructuraEvaluable _formula;

	/**
	 * Texto de la fórmula.
	 */
	protected String _textoFormula;

	/** 
	 * Nombre del nodo.
	 */
	private String _nombre;

	/** 
	 * Devuelve el nombre del Nodo.
	 */
	public String getNombre() { 
		return this._nombre;
	}

	/**
	 * Define un nombre nuevo para el nodo.
	 */
	public void setNombre(String nombre) {
		this._nombre = new String(nombre);
	}


	/**
	 * Agrega un dependiente.
	 *
	 * @param dependiente
	 *
	 * @throws ExcepcionLlaveExistente En el caso de que la llave del 
	 * dependiente ya esté contenida.
	 */
	public synchronized void agregarDependiente(Nodo dependiente)
			throws ExcepcionLlaveExistente
	{
		if (_dependientes.containsKey(dependiente.getLlave()))
			throw new ExcepcionLlaveExistente("Nodo -> agregarDependiente");

		_dependientes.put(dependiente.getLlave(), dependiente);
	}

	/**
	 * Quita un dependiente. 
	 *
	 * @param llave
	 *
	 * @throws ExcepcionLlaveInexistente El dependiente a quitar no se encuentra.
	 */
	public synchronized void quitarDependiente(Long llave)
			throws ExcepcionLlaveInexistente
	{
		if (!_dependientes.containsKey(llave))
			throw new ExcepcionLlaveInexistente("Nodo -> quitarDependiente");

		_dependientes.remove(llave);
	}


	/**
	 * Agrega un superior de este objeto. 
	 *
	 * @throws ExcepcionLlaveExistente La llave del objeto ya ha sido 
	 * agregada.
	 */
	public synchronized void agregarSuperior(Nodo superior)
			throws ExcepcionLlaveExistente
	{
		if (_superiores.containsKey(superior.getLlave()))
			throw new ExcepcionLlaveExistente("Nodo -> agregarSuperior");

		_superiores.put(superior.getLlave(), superior);
	}

	/**
	 * Elimina objeto del cual depende este objeto. Verifica que el Hashtable
	 * contenga la llave.
	 * 
	 * @throws ExcepcionLlaveInexistente En el caso de que la llave del 
	 * superior no exista.
	 */
	public synchronized void quitarSuperior(Long llave)
			throws ExcepcionLlaveInexistente
	{
		if (!_superiores.containsKey(llave))
			throw new ExcepcionLlaveInexistente("Nodo -> quitarSuperior");

		_superiores.remove(llave);
	}


	/**
	 * Verifica la existencia de algún ciclo
	 * 
	 * @return True si existe un ciclo y false de lo contrario
	 */
	public boolean existeCiclo(Long llave)
	{
		if (this._llave == llave)
			return true;

		for (ISuperior superior : _superiores.values())
		{
			if (superior.existeCiclo(llave))
				return true;
		}

		return false;
	}


	/**
	 * Actualiza cada uno de los dependientes (observadores). Método que se 
	 * llama en caso de que los valores del nodo actual se hayan modificado.
	 */
	public void actualizarDependientes()
	{
		for (Nodo dependiente : _dependientes.values())
		{
			dependiente.actualizar();
		}
	}


	/**
	 * Recalcula el valor de éste objeto. Es llamado cuando un dependiente
	 * (observador) necesita ser actualizado. Se vale de actualizarValor para
	 * actualizar el valor propio de este objeto.
	 * Envía a actualizar a otros dependientes.
	 */
	public void actualizar()
	{
		actualizarValor();
		actualizarDependientes();
	}
	

	/**
	 * Recalcula el valor del elemento. Principalmente se utiliza cuando
	 * un superior así lo solicita, porque seguramente han cambiado los
	 * valores.
	 */
	protected abstract void actualizarValor();

	/**
	 * Pregunta si este objeto tiene dependientes de él o no. Sirve en el caso
	 * de que se quiera eliminar.
	 */
	public boolean tieneDependientes()
	{
		return (!_dependientes.isEmpty());
	}

	public void liberarDependientes() {
		for (Nodo dependiente : _dependientes.values())
			dependiente.independizate();
	}

	/** 
	 * Pregunta si el objeto es independiente o no.
	 */
	public boolean esIndependiente() {
		return (this._textoFormula == null);
	}

	/**
	 * Independiza a un gestionable. Lo que hace es decirle a cada uno de los
	 * objetos de los cuales depende este objeto (sus superiores), que ya no va 
	 * a depender de ellos. Además quita a <em>todos</em> del arreglo de superiores:
	 * si ya no depende de alguno de los elementos de la fórmula, no puede 
	 * depender de ninguno de ellos.
	 *
	 */
	public void independizate() throws ExcepcionLlaveInexistente
	{
		for (ISuperior superior : _superiores.values())
			superior.quitarDependiente(this.getLlave());
		this._textoFormula = null;
		this._formula = null;
		_superiores.clear();
	}

	public boolean tieneDependientesExternos() {
		for (Nodo dependiente : _dependientes.values())
			if (dependiente.getGestor() != this.getGestor())
				return true;
		return false;
	}

	/**
	 * Guarda la fórmula digitada por el usuario.
	 *
	 * @todo Debe modificarse cada nombre por la referencia respectiva.
	 */
	public void setTextoFormula(String formulaDigitada)
	{
		this._textoFormula = formulaDigitada;
	}

	/**
	 * Asigna una nueva fórmula. Ya se debe de haber inicializado el atributo,
	 * es decir, Java debe encargarse de determinar el tipo del atributo. Se
	 * utiliza el método prefijo, en el cual se colocan las funciones, y para
	 * cada función los argumentos correspondientes.
	 *
	 * @param formula Estructura evaluable que permite generar los valores
	 * y actualizarlos.
	 *
	 * @return El vector del texto de los valores.
	 */
	public void setFormula(EstructuraEvaluable formula)
	{
		this._formula = formula;
		this._formula.evaluar((IEvaluable) this);
	}


	public abstract void insertarCasos(int indice, int noCasos);

	public abstract void eliminarCasos(int indice, int noCasos);

	/**
	 * Devuelve el texto de la fórmula de la cual depende este atributo.
	 *
	 * @todo Debe modificarse la referencia a cada uno de los atributos por
	 * una su nombre.
	 */
	public String getTextoFormula()
	{
		return this._textoFormula;
	}

	public abstract String getTextoValor(int indice);

	public abstract String[] getTextoValores();

	private TipoValor _tipo;

	public abstract TipoValor getTipo();

	public abstract String set(int indice, String valor);

}
